//
//  HttpUploader.swift
//  iUtil
//
//  Created by wendachuan on 19/3/22.
//  Copyright © 2019年 wendachuan. All rights reserved.
//

import Foundation
import AFNetworking
import Photos

/// http 上传助手
open class HttpUploader: NSObject {
    /// http请求超时时间
    public static var requestTimeout = TimeInterval(10)
    
    weak var delegate: HttpUploaderDelegate?
    var requestSerializer = AFHTTPRequestSerializer()
    var responseSerializer = AFHTTPResponseSerializer()

    fileprivate var uploadAddress: String
    fileprivate var uploadSession: URLSessionUploadTask?

    init(address: String) {
        uploadAddress = address
    }

    /// 上传资源
    ///
    /// - parameter asset: 资源
    /// - parameter parameters: 写入http请求body的参数
    open func uploadAsset(_ asset: PHAsset, parameters: [String: Any]?) {
        uploadSession = HttpUploader.uploadAsset(uploadAddress, requestSerializer: requestSerializer, responseSerializer: responseSerializer, asset: asset, parameters: parameters, reportProgress: { (progress) in
            self.delegate?.uploadReportProgress(self, progress: Float(progress.completedUnitCount) / Float(progress.totalUnitCount))
            }, success: { (responseObject) in
                self.delegate?.uploadSuccess(self, withResponseObject: responseObject)
            }, failure: { (error) in
                self.delegate?.uploadFailure(self, error: error)
            }, canceled: {
                self.delegate?.uploadCanceled(self)
            }
        )
        uploadSession?.resume()
    }

    /// 上传数据
    ///
    /// - parameter header: 写入http请求头部的信息
    /// - parameter body: http body
    open func uploadData(requestHeader header: [String: String]? = nil, body: [String : Any]) {
        uploadSession = HttpUploader.uploadData(uploadAddress, requestSerializer: requestSerializer, responseSerializer: responseSerializer, body: body, reportProgress: { [weak self] (progress) in
            guard let strongSelf = self else { return }
            strongSelf.delegate?.uploadReportProgress(strongSelf, progress: Float(progress.completedUnitCount) / Float(progress.totalUnitCount))
            }, success: { [weak self] (responseObject) in
                guard let strongSelf = self else { return }
                strongSelf.delegate?.uploadSuccess(strongSelf, withResponseObject: responseObject)
            }, failure: { [weak self] (error) in
                guard let strongSelf = self else { return }
                strongSelf.delegate?.uploadFailure(strongSelf, error: error)
            }, canceled: { [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.delegate?.uploadCanceled(strongSelf)
        })
        uploadSession?.resume()
    }

    /// 取消上传
    open func cancel() {
        uploadSession?.cancel()
    }
}

/// http上传委托
public protocol HttpUploaderDelegate: class {
    func uploadReportProgress(_ uploader: HttpUploader, progress: Float)
    func uploadSuccess(_ uploader: HttpUploader, withResponseObject object: Any?)
    func uploadFailure(_ uploader: HttpUploader, error: NSError)
    func uploadCanceled(_ uploader: HttpUploader)
}

extension HttpUploader {
    /// 上传图片
    ///
    /// - parameter sessionManager:
    /// - parameter toAddress:   目标地址
    /// - parameter asset:   资源
    /// - parameter parameters:  form参数
    /// - parameter reportProgress:  上传进度汇报
    /// - parameter success: 上传成功后调用的block
    /// - parameter failure: 上传失败后调用的block
    /// - parameter canceled: 取消上传后调用的block
    /// - returns: 上传任务创建成功则返回NSURLSessionUploadTask对象，否则返回nil
    public static func uploadImageAsset(_ sessionManager: AFHTTPSessionManager, toAddress: String, asset: PHAsset, parameters: [String : Any]?, reportProgress: @escaping (_ progress: Progress) -> Void, success: @escaping (_ responseObject: Any?) -> Void, failure: @escaping (_ error: NSError) -> Void, canceled: @escaping () -> Void ) -> URLSessionUploadTask? {

        var sessionTask: URLSessionUploadTask? = nil
        let semaphore = DispatchSemaphore(value: 0)
        let options = PHImageRequestOptions()
        options.isSynchronous = true
        PHImageManager.default().requestImageData(for: asset, options: options, resultHandler: { (data, fileName, _, _) in
            var error: NSError? = nil
            let request = sessionManager.requestSerializer.multipartFormRequest(withMethod: "POST", urlString: toAddress, parameters: parameters, constructingBodyWith: { (formData) in
                formData.appendPart(withFileData: data!, name: "file", fileName: fileName!, mimeType: asset.mimeType)
                }, error: &error)

            if error == nil {
                sessionTask = sessionManager.uploadTask(withStreamedRequest: request as URLRequest, progress: { (progress) in
                    reportProgress(progress)
                    }, completionHandler: { (response, responseObject, error) in
                        if let error = error as NSError? {
                            if error.code != NSURLErrorCancelled {
                                failure(NSError(errorCode: error.code, errorDescription: (error.localizedDescription)))
                            } else {
                                canceled()
                            }
                        } else {
                            HttpHelper.parseHttpResponse(response, responseObject: responseObject, success: { (responseObject) in
                                success(responseObject)
                                }, failure: { (error) in
                                    failure(error)
                            })
                        }
                })

                sessionTask!.resume()
            } else {
                failure(NSError(errorCode: error!.code, errorDescription: (error!.localizedDescription)))
            }
            semaphore.signal()
        })
        semaphore.wait()
        return sessionTask
    }

    /// iOS 9.0以前的版本上传视频
    ///
    /// - parameter sessionManager:
    /// - parameter toAddress:   目标地址
    /// - parameter asset:   资源
    /// - parameter parameters:  form参数
    /// - parameter reportProgress:  上传进度汇报
    /// - parameter success: 上传成功后调用的block
    /// - parameter failure: 上传失败后调用的block
    /// - parameter canceled: 取消上传后调用的block
    /// - returns: 上传任务创建成功则返回NSURLSessionUploadTask对象，否则返回nil
    public static func uploadVideoBefore9_0(_ sessionManager: AFHTTPSessionManager, toAddress: String, asset: PHAsset, parameters: [String : Any]?, reportProgress: @escaping (_ progress: Progress) -> Void, success: @escaping (_ responseObject: Any?) -> Void, failure: @escaping (_ error: NSError) -> Void, canceled: @escaping () -> Void ) -> URLSessionUploadTask? {

        var sessionTask: URLSessionUploadTask? = nil
        let semaphore = DispatchSemaphore(value: 0)
        PHImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler: { (avAsset, _, _) in
            if let avURLAsset = avAsset as? AVURLAsset {
                var error: NSError? = nil
                var formError: NSError? = nil
                let request = sessionManager.requestSerializer.multipartFormRequest(withMethod: "POST", urlString: toAddress, parameters: parameters, constructingBodyWith: { (formData) in
                    do {
                        try formData.appendPart(withFileURL: avURLAsset.url, name: "file")
                    } catch let err as NSError {
                        formError = err
                    }
                    }, error: &error)

                if formError != nil {
                    error = formError
                }

                if error == nil {
                    sessionTask = sessionManager.uploadTask(withStreamedRequest: request as URLRequest, progress: { (progress) in
                        reportProgress(progress)

                        }, completionHandler: { (response, responseObject, error) in
                            if let error = error as NSError? {
                                if error.code != NSURLErrorCancelled {
                                    failure(NSError(errorCode: error.code, errorDescription: (error.localizedDescription)))
                                } else {
                                    canceled()
                                }
                            } else {
                                HttpHelper.parseHttpResponse(response, responseObject: responseObject, success: { (responseObject) in
                                    success(responseObject)
                                    }, failure: { (error) in
                                        failure(error)
                                })
                            }
                    })
                    sessionTask?.resume()
                } else {
                    failure(NSError(errorCode: error!.code, errorDescription: (error!.localizedDescription)))
                }
            } else {
                failure(NSError(errorCode: 0, errorDescription: "程序逻辑错误!!!PHAsset类型为视频但不是AVURLAsset!!!"))
            }
            semaphore.signal()
        })
        semaphore.wait()
        return sessionTask
    }

    /// 将视频导入到沙盒
    ///
    /// - parameter asset:   资源
    /// - parameter success: 导入成功后调用的block
    /// - parameter failure: 导入失败后调用的block
    @available(iOS 9.0, *)
    public static func importAVAssetToSandBox(_ asset: PHAsset, success: @escaping (_ localFileURL: URL) -> Void, failure: @escaping (_ error: NSError) -> Void ) {

        if let resource = PHAssetResource.assetResources(for: asset).first {
            let fileName = resource.originalFilename
            let now = Date()
            let tmpFilePath = NSTemporaryDirectory() + "\(now.timeIntervalSince1970)\(fileName)"
            let localFileURL = URL(fileURLWithPath: tmpFilePath)
            let options = PHAssetResourceRequestOptions()
            PHAssetResourceManager.default().writeData(for: resource, toFile: localFileURL, options: options, completionHandler: { (error) in
                if let error = error as NSError? {
                    failure(NSError(errorCode: error.code, errorDescription: error.localizedDescription))
                } else {
                    success(localFileURL)
                }
            })
        }
    }

    /// iOS 9.0版本上传视频
    ///
    /// - parameter sessionManager:
    /// - parameter toAddress:   目标地址
    /// - parameter asset:   资源
    /// - parameter parameters:  form参数
    /// - parameter reportProgress:  上传进度汇报
    /// - parameter success: 上传成功后调用的block
    /// - parameter failure: 上传失败后调用的block
    /// - parameter canceled: 取消上传后调用的block
    /// - returns: 上传任务创建成功则返回NSURLSessionUploadTask对象，否则返回nil
    @available(iOS 9.0, *)
    public static func uploadVideo9_0(_ sessionManager: AFHTTPSessionManager, toAddress: String, asset: PHAsset, parameters: [String : Any]?, reportProgress: @escaping (_ progress: Progress) -> Void, success: @escaping (_ responseObject: Any?) -> Void, failure: @escaping (_ error: NSError) -> Void, canceled: @escaping () -> Void ) -> URLSessionUploadTask? {

        var sessionTask: URLSessionUploadTask? = nil
        importAVAssetToSandBox(asset, success: { (localFileURL) in
            let semaphore = DispatchSemaphore(value: 0)
            PHImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler: { (_, _, _) in
                var error: NSError? = nil
                var formError: NSError? = nil
                let request = sessionManager.requestSerializer.multipartFormRequest(withMethod: "POST", urlString: toAddress, parameters: parameters, constructingBodyWith: { (formData) in
                    do {
                        try formData.appendPart(withFileURL: localFileURL, name: "file")
                    } catch let err as NSError {
                        formError = err
                    }
                    }, error: &error)

                if formError != nil {
                    error = formError
                }

                if error == nil {
                    sessionTask = sessionManager.uploadTask(withStreamedRequest: request as URLRequest, progress: { (progress) in
                        reportProgress(progress)

                        }, completionHandler: { (response, responseObject, error) in
                            if let error = error as NSError? {
                                if error.code != NSURLErrorCancelled {
                                    failure(NSError(errorCode: error.code, errorDescription: (error.localizedDescription)))
                                } else {
                                    canceled()
                                }
                            } else {
                                HttpHelper.parseHttpResponse(response, responseObject: responseObject, success: { (responseObject) in
                                    success(responseObject)
                                    }, failure: { (error) in
                                        failure(error)
                                })
                            }
                    })
                    sessionTask?.resume()
                } else {
                    failure(NSError(errorCode: error!.code, errorDescription: (error!.localizedDescription)))
                }

                semaphore.signal()
            })
            
            semaphore.wait()
        }) { (error) in
            failure(error)
        }
        return sessionTask
    }

    /// 上传视频
    ///
    /// - parameter sessionManager:
    /// - parameter toAddress:   目标地址
    /// - parameter asset:   资源
    /// - parameter parameters:  form参数
    /// - parameter reportProgress:  上传进度汇报
    /// - parameter success: 上传成功后调用的block
    /// - parameter failure: 上传失败后调用的block
    /// - parameter canceled: 取消上传后调用的block
    /// - returns: 上传任务创建成功则返回NSURLSessionUploadTask对象，否则返回nil
    public static func uploadVideoAsset(_ sessionManager: AFHTTPSessionManager, toAddress: String, asset: PHAsset, parameters: [String : Any]?, reportProgress: @escaping (_ progress: Progress) -> Void, success: @escaping (_ responseObject: Any?) -> Void, failure: @escaping (_ error: NSError) -> Void, canceled: @escaping () -> Void ) -> URLSessionUploadTask? {

        if #available(iOS 9.0, *) {
            return uploadVideo9_0(sessionManager, toAddress: toAddress, asset: asset, parameters: parameters, reportProgress: reportProgress, success: success, failure: failure, canceled: canceled)
        } else {
            return uploadVideoBefore9_0(sessionManager, toAddress: toAddress, asset: asset, parameters: parameters, reportProgress: reportProgress, success: success, failure: failure, canceled: canceled)
        }
    }

    /// http 文件上传
    ///
    /// - parameter toAddress:   目标地址
    /// - parameter requestSerializer:   http请求序列化使用的对象
    /// - parameter responseSerializer:  http返回值序列化使用的对象
    /// - parameter asset:   资源
    /// - parameter parameters:   form参数
    /// - parameter reportProgress:  上传进度汇报
    /// - parameter success: 上传成功后调用的block
    /// - parameter failure: 上传失败后调用的block
    /// - parameter canceled: 取消上传后调用的block
    /// - returns: 上传任务创建成功则返回NSURLSessionUploadTask对象，否则返回nil
    public static func uploadAsset(_ toAddress: String, requestSerializer: AFHTTPRequestSerializer, responseSerializer: AFHTTPResponseSerializer, asset: PHAsset, parameters: [String : Any]?, reportProgress: @escaping (_ progress: Progress) -> Void, success: @escaping (_ responseObject: Any?) -> Void, failure: @escaping (_ error: NSError) -> Void, canceled: @escaping () -> Void ) -> URLSessionUploadTask? {

        let sessionConfiguration = URLSessionConfiguration.default
        sessionConfiguration.timeoutIntervalForRequest = requestTimeout

        let sessionManager = AFHTTPSessionManager(sessionConfiguration: sessionConfiguration)
        sessionManager.requestSerializer = requestSerializer
        sessionManager.responseSerializer = responseSerializer
        sessionManager.completionQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)

        if asset.mediaType == PHAssetMediaType.image {
            return uploadImageAsset(sessionManager, toAddress: toAddress, asset: asset, parameters: parameters, reportProgress: reportProgress, success: success, failure: failure, canceled: canceled)
        } else if asset.mediaType == PHAssetMediaType.video {
            return uploadVideoAsset(sessionManager, toAddress: toAddress, asset: asset, parameters: parameters, reportProgress: reportProgress, success: success, failure: failure, canceled: canceled)
        } else {
            failure(NSError(errorCode: 0, errorDescription: "PHAsset类型\(asset.mediaType)不支持"))
            return nil
        }
    }

    /// http 数据上传
    ///
    /// - parameter toAddress:   目标地址
    /// - parameter requestSerializer:   http请求序列化使用的对象
    /// - parameter responseSerializer:  http返回值序列化使用的对象
    /// - parameter header:   写入到http header的参数
    /// - parameter body: 写入到http body的数据
    /// - parameter reportProgress:  上传进度汇报
    /// - parameter success: 上传成功后调用的block
    /// - parameter failure: 上传失败后调用的block
    /// - parameter canceled: 取消上传后调用的block
    /// - returns: 上传任务创建成功则返回NSURLSessionUploadTask对象，否则返回nil
    public static func uploadData(_ toAddress: String, requestSerializer: AFHTTPRequestSerializer, responseSerializer: AFHTTPResponseSerializer, requestHeader header: [String : String]? = nil, body: [String : Any], reportProgress: @escaping (_ progress: Progress) -> Void, success: @escaping (_ responseObject: Any?) -> Void, failure: @escaping (_ error: NSError) -> Void, canceled: @escaping () -> Void ) -> URLSessionUploadTask? {
        let sessionConfiguration = URLSessionConfiguration.default
        sessionConfiguration.timeoutIntervalForRequest = requestTimeout

        let sessionManager = AFHTTPSessionManager(sessionConfiguration: sessionConfiguration)
        sessionManager.requestSerializer = requestSerializer
        sessionManager.responseSerializer = responseSerializer
        sessionManager.completionQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)

        var error: NSError? = nil
        let request = AFJSONRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: toAddress, parameters: body, constructingBodyWith: nil, error: &error)
        if header != nil {
            for (field, value) in header! {
                request.setValue(value, forHTTPHeaderField: field)
            }
        }

        if error != nil {
            failure(NSError(errorCode: error!.code, errorDescription: (error!.localizedDescription)))
            return nil
        }

        let sessionTask = sessionManager.uploadTask(with: request as URLRequest, from: nil, progress: { (progress) in
            reportProgress(progress)
            }, completionHandler: { (response, responseObject, error) in
                if let error = error as NSError? {
                    if error.code != NSURLErrorCancelled {
                        failure(NSError(errorCode: error.code, errorDescription: (error.localizedDescription)))
                    } else {
                        canceled()
                    }
                } else {
                    HttpHelper.parseHttpResponse(response, responseObject: responseObject, success: { (responseObject) in
                        success(responseObject)
                        }, failure: { (error) in
                            failure(error)
                    })
                }
        })

        sessionTask.resume()
        return sessionTask
    }
}
